package display.structure;

import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import common.Common;

import data.news.Article;
import data.news.db.NewsDB;
import data.process.CurveGen;
import data.process.DocInfo;
import display.CurveDisplay;
import display.WheelDisplay;
import display.renderer.BagTreeMapRenderer;

import prefuse.Constants;
import prefuse.data.CascadedTable;
import prefuse.data.Graph;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.io.DataIOException;
import prefuse.data.tuple.TupleSet;
import prefuse.data.util.Index;
import prefuse.render.DefaultRendererFactory;
import prefuse.util.ColorLib;
import prefuse.util.DataLib;
import prefuse.util.MathLib;
import prefuse.visual.EdgeItem;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.InGroupPredicate;
import prefuse.visual.sort.ItemSorter;
import weeny.pre.util.Geom;
import weeny.pre.util.IntColor;
import weeny.pre.util.Pre;
import weeny.recycle.ArraySupportedGraphReader;
import weeny.recycle.ArraySupportedGraphWriter;
import weeny.recycle.Pair;
import weeny.util.logs.Debug;

@SuppressWarnings("deprecation")
public class Belt {
	public boolean clockwise = false;
	public String name = "belt_bag";
	public Point2D center;
	public double radius;
	public double width;
	public Table docData = null;
	public Table bagTable = null;
	public Point2D otherEnd = null;
	
	private WheelDisplay display = null;
	
	private int moreColor = ColorLib.rgb(0, 53, 0);//ColorLib.rgb(200, 200, 200);//ColorLib.rgb(164,213,189);
	private int lessColor = ColorLib.rgb(112, 165, 95);//ColorLib.rgb(0,77,37);
	
    private double bagSize(Tuple bagContent){
    	return Math.sqrt(((long[]) bagContent.get("group")).length);
    }
    private double bagLength(Tuple bagContent){
    	Index index = docData.index(Article.FLD_ID);
    	long[] ids = (long[]) bagContent.get("group");
    	double length = 0;
    	for (long id : ids) {
    		Tuple docTuple = docData.getTuple(index.get(id)); 
    		length += docTuple.getDouble("length");
		}
    	length = length / ids.length / 3;
    	length = length > radius ? radius : length;
    	length = length > 10 ? length : 10;
    	return length;
    }
	public Belt(Point2D center, double radius, double width, Point2D otherEnd, WheelDisplay display){
		this.center = center;
		this.radius = radius;
		this.width = width;
		this.otherEnd = otherEnd;
		this.display = display;
		display.focusTupleSet(visualBagGroup).addTupleSetListener(visualBagGroupListener);
		display.focusTupleSet(focusBagGroup).addTupleSetListener(focusBagGroupListener);
	}
	private TupleSetListener visualBagGroupListener = new TupleSetListener() {
		public void tupleSetChanged(TupleSet tset, Tuple[] added, Tuple[] removed) {
			for (Tuple tuple : removed) {
				hideBar(tuple);
			}
			for (Tuple tuple : added) {
				showBar(tuple);
			}
		}
	};
	private TupleSetListener focusBagGroupListener = new TupleSetListener() {
		public void tupleSetChanged(TupleSet tset, Tuple[] added, Tuple[] removed) {
			for (Tuple tuple : removed) {
				removeFocusBag(tuple);
			}
			for (Tuple tuple : added) {
				addFocusBag(tuple);
			}
			addOrRemoveFocusBag();
		}
	};
	
	private void addFocusBag(Tuple bag){
		EdgeItem bar = barItem(bag);
		Pre.setStkCol(bar, IntColor.red(255));
		if (highlightedBag != null) {
			EdgeItem temp = barItem(highlightedBag);
			Pre.setStkCol(temp, temp.getFillColor());
		}
		highlightedBag = bag;
//		setSpeed(speedTable.getDouble(bag.getRow(), CurveGen.CURVE_Y)*Math.signum(getSpeed()));
	}

	private double totalFocusKeyCntSoFar = 0;
	private void addOrRemoveFocusBag(){
		updateDisplayDate();
		double sum = 0;
		for(Iterator<Tuple> it = display.focusTupleSet(focusBagGroup).tuples(); it.hasNext();){
			Tuple bag = it.next();
			sum += bag.getInt("keyCnt");
		}
		if (sum > totalFocusKeyCntSoFar) {
			totalFocusKeyCntSoFar = sum;
		}
		totalFocusKeyCntSoFar *= 0.95;
		double speedValue = (totalFocusKeyCntSoFar-sum)/totalFocusKeyCntSoFar*2*minimumSpeed+minimumSpeed;
		setSpeed(Math.signum(getSpeed())*speedValue);
//		System.out.println("speed = "+getSpeed());
	}
	public double minimumSpeed = 40;

	private void removeFocusBag(Tuple bag){
		EdgeItem bar = barItem(bag);
		Pre.setStkCol(bar, bar.getFillColor());
	}
	
    public int outColor = ColorLib.rgb(83, 113, 152);
    public int innColor = ColorLib.rgb(160, 216,239);
    
    
    private List<Table> rawEntropyCurve = null;
    public CurveDisplay curveDisplay = null;
    public void setupCurveDisplay(){
    	rawEntropyCurve = new ArrayList<Table>();
    	List<DocInfo> infoList = CurveGen.collectInfo(this.companyName);
		String rawFile = Common.wheelFolder(this.companyName)+"raw_entropy.xml";
		Table rawCurve = null;
		if (! new File(rawFile).exists()) {
//	    	rawEntropyCurve.add(CurveGen.absoluteEntropyCurve(infoList));
			rawCurve = CurveGen.conditionalEntropyCurve(infoList);
			Graph g = new Graph(rawCurve, false);
			try {
				new ArraySupportedGraphWriter().writeGraph(g, new File(rawFile));
			} catch (DataIOException e) {
				e.printStackTrace();
			}
		}
		else{
			Debug.msg("read curve file from "+rawFile);
			try {
				rawCurve = new ArraySupportedGraphReader().readGraph(new File(rawFile)).getNodeTable();
			} catch (DataIOException e) {
				e.printStackTrace();
			}
		}
		rawEntropyCurve.add(rawCurve);
		
    	List<Table> curveTableList = new ArrayList<Table>();
    	for(Table table : rawEntropyCurve){
    		Map<Integer, Tuple> id2Tuple = new HashMap<Integer, Tuple>();
    		for(Iterator<Tuple> it = table.tuples(); it.hasNext(); ){
    			Tuple tuple = it.next();
    			id2Tuple.put((int) tuple.getLong(CurveGen.ID), tuple);
    		}
    		CascadedTable casTable = new CascadedTable(bagTable);
    		casTable.addColumns(CurveGen.CURVE_SCHEMA);
    		int index = 0;
    		double yValue = 0;
    		speedTable = CurveGen.CURVE_SCHEMA.instantiate();
    		for (Iterator<Tuple> it = casTable.tuples(); it.hasNext(); index ++){
    			Tuple bagCurveTuple = it.next();
    			long[] ids = (long[]) bagCurveTuple.get("group");
    			if (ids != null && ids.length != 0){
    				yValue = 0;
        			for (long id : ids) {
    					yValue += id2Tuple.get((int)id).getDouble(CurveGen.CURVE_Y);
    				}
        			yValue /= ids.length;
    			}
    			else{
    				yValue /=2;
    			}
    			bagCurveTuple.setDouble(CurveGen.CURVE_X, index);
    			bagCurveTuple.setDouble(CurveGen.CURVE_Y, yValue);
    			{//adding to speedTable for smoothing
    				Tuple tuple = speedTable.getTuple(speedTable.addRow());
    				tuple.setDouble(CurveGen.CURVE_X, index);
    				tuple.setDouble(CurveGen.CURVE_Y, yValue);
    			}
    			
    		}
    		{//tune speed
    			CurveGen.smooth(speedTable, new double[]{0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2});
    			double max = DataLib.max(speedTable, CurveGen.CURVE_Y).getDouble(CurveGen.CURVE_Y);
    			double min = DataLib.min(speedTable, CurveGen.CURVE_Y).getDouble(CurveGen.CURVE_Y);
    			Debug.info(0, "output the speed information");
    			for(Iterator<Tuple> it = speedTable.tuples(); it.hasNext();){
    				Tuple tuple = it.next();
    				double frac = MathLib.linearInterp(tuple.getDouble(CurveGen.CURVE_Y), min, max);
    				tuple.setDouble(CurveGen.CURVE_Y, 50*(1-frac)+10);
    				System.out.println(speed+":"+tuple.getDouble(CurveGen.CURVE_Y));
    			}
    		}
    		
    		CurveGen.smooth(casTable, new double[]{0.2,0.2,0.2,0.2,0.2,0.2,0.2});
    		curveTableList.add(casTable);
    	}
    	curveDisplay = new CurveDisplay(curveTableList, CurveGen.CURVE_X, CurveGen.CURVE_Y, this);
    	curveDisplay.setSize(WheelDisplay.DISPLAY_WIDTH, 200);
    }
    private Table speedTable;
    public void setupSpeedTableAccordingToKeyCnt(){
    	int maxKeyCnt = DataLib.max(bagTable, "keyCnt").getInt("keyCnt");
    	Debug.info(0, "setup speed table according to key count");
    	for(Iterator<Tuple> it = speedTable.tuples(); it.hasNext();){
    		Tuple speed = it.next();
    		Tuple bag = bagTable.getTuple(speed.getRow());
    		speed.setDouble(CurveGen.CURVE_Y, (maxKeyCnt - bag.getInt("keyCnt"))*40.0/maxKeyCnt+20);
    		System.out.println(speed.getDouble(CurveGen.CURVE_Y));
    	}
    }
    
    public String companyName = null;
    public void loadDocInfo(String companyName){
    	this.companyName = companyName;
    	String file = Common.wheelFolder(companyName)+"docInfo.xml";
		try {
			docData = new ArraySupportedGraphReader().readGraph(file).getNodeTable();
		} catch (DataIOException e1) {
			e1.printStackTrace();
		}
    }
    SimpleDateFormat integerDateFormat = new SimpleDateFormat("yyyyMMdd");
	public void groupByDay(int dayNum){
		bagTable = new Table();
		bagTable.addColumn("group", long[].class, new long[0]);
		bagTable.addColumn("sort",  int[].class, new int[0]);
		bagTable.addColumn("keyCnt", int.class, 1);
		bagTable.addColumn("date", int.class);
		List<Long> idList = new ArrayList<Long>();
		List<Integer> dateList = new ArrayList<Integer>();
		for(Iterator<Tuple> it = docData.tuples(); it.hasNext();){
			Tuple tp = it.next();
			idList.add(tp.getLong(Article.FLD_ID));
			dateList.add(tp.getInt(NewsDB.FLD_DATE));
		}
		final Index docIndex = docData.index(Article.FLD_ID);
		Collections.sort(idList, new Comparator<Long>(){
			public int compare(Long o1, Long o2) {
				int d1 = docData.getInt(docIndex.get(o1.longValue()), NewsDB.FLD_DATE);
				int d2 = docData.getInt(docIndex.get(o2.longValue()), NewsDB.FLD_DATE);
				return d1>d2? 1 : d1<d2? -1 : 0;
			}
		});
		Collections.sort(dateList);
		long startTime = -1;
		long step = dayNum*1000L*24*3600;
		int currDate = 0;
		try {
			startTime = integerDateFormat.parse(dateList.get(0)+"").getTime()+step;
			int sortIdx = 0;
			List<Long> ids = new ArrayList<Long>();
			List<Integer> days = new ArrayList<Integer>();
			for(; sortIdx<dateList.size();startTime+=step){
				ids.clear();
				days.clear();
				long currentTime = integerDateFormat.parse(dateList.get(sortIdx)+"").getTime();
				while (currentTime < startTime){
					days.add(dateList.get(sortIdx));
					ids.add(idList.get(sortIdx));
					sortIdx++;
					if (sortIdx==dateList.size()) {
						break;
					}
					currentTime = integerDateFormat.parse(dateList.get(sortIdx)+"").getTime();
				}
				long[] group = new long[ids.size()];
				int[]  sort = new int[ids.size()];
				for (int j = 0; j<ids.size(); j++){
					group[j] = ids.get(j);
					sort[j] = days.get(j);
				}
				if (sort.length>0) {
					currDate = sort[0];
				}
				Tuple bagTuple = bagTable.getTuple(bagTable.addRow());
				bagTuple.set("group", group);
				bagTuple.set("sort",  sort);
				bagTuple.setInt("date", currDate);
				bagTuple.set("keyCnt", 0);
			}
		} catch (ParseException e1) {
			e1.printStackTrace();
		}
		initBagTable(bagTable);
	}
	public void groupByDocNum(int docNum){
		bagTable = new Table();
		bagTable.addColumn("group", long[].class);
		bagTable.addColumn("sort",  int[].class);
		bagTable.addColumn("keyCnt", int.class, 1);
		bagTable.addColumn("date", Date.class);
		List<Long> idList = new ArrayList<Long>();
		List<Integer> dateList = new ArrayList<Integer>();
		for(Iterator<Tuple> it = docData.tuples(); it.hasNext();){
			Tuple tp = it.next();
			idList.add(tp.getLong(Article.FLD_ID));
			dateList.add(tp.getInt(NewsDB.FLD_DATE));
		}
		final Index docIndex = docData.index(Article.FLD_ID);
		Collections.sort(idList, new Comparator<Long>(){
			public int compare(Long o1, Long o2) {
				int d1 = docData.getInt(docIndex.get(o1.longValue()), NewsDB.FLD_DATE);
				int d2 = docData.getInt(docIndex.get(o2.longValue()), NewsDB.FLD_DATE);
				return d1>d2? 1 : d1<d2? -1 : 0;
			}
		});
		Collections.sort(dateList);
		int cnt = 0;
		for(cnt = 0; (cnt+1)*docNum<idList.size(); cnt++){
			Tuple bagTuple = bagTable.getTuple(bagTable.addRow());
			if (bagTuple.getRow()!=cnt) {
				throw new IllegalStateException("FIXME");
			}
			long[] ids = new long[docNum];
			int[] sort = new int[docNum];
			for(int i = 0; i< ids.length; i++){
				ids[i] = idList.get(cnt*docNum+i);
				sort[i]= dateList.get(cnt*docNum+i);
			}
			bagTuple.set("group", ids);
			bagTuple.set("sort",  sort);
			bagTuple.setInt("date", sort[0]);
			bagTuple.set("keyCnt", 0);
		}
		int length = idList.size()-cnt*docNum;
		long[] ids = new long[length];
		int[] sort = new int[length];
		Tuple bagTuple = bagTable.getTuple(bagTable.addRow());
		if (bagTuple.getRow()!=cnt) {
			throw new IllegalStateException("FIXME");
		}
		for(int i = 0; i< ids.length; i++){
			ids[i] = idList.get(cnt*docNum+i);
			sort[i]= dateList.get(cnt*docNum+i);
		}
		bagTuple.set("group", ids);
		bagTuple.set("sort",  sort);
		bagTuple.setInt("date", sort[0]);
		bagTuple.setInt("keyCnt", 0);
		initBagTable(bagTable);
	}
	public double getBagLength(Tuple bagContent){
		return bagContent.getDouble(BAG_LENGTH);
	}
	public Tuple[] getDocTuples(Tuple bagContent){
		long[] ids = (long[]) bagContent.get("group");
		if (ids == null) {
			return null;
		}
		Tuple[] result = new Tuple[ids.length];
		Index index = docData.index(Article.FLD_ID);
		for(int i = 0; i<ids.length; i++){
			result[i] = docData.getTuple(index.get(ids[i]));
		}
		return result;
	}
	public EdgeItem barItem(Tuple bag){
		return (EdgeItem) bag.get(VISUAL_BAR);
	}
	public static String VISUAL_BAR = "visual_bar";
	private static String BAG_LENGTH = "bag_length";
	private void makeVisualBar(Table table){
		table.addColumn(VISUAL_BAR, EdgeItem.class);
		table.addColumn(BAG_LENGTH, double.class);
		DefaultRendererFactory drf = (DefaultRendererFactory) display.getVisualization().getRendererFactory();
		final ItemSorter sorter = display.getItemSorter();
    	display.setItemSorter(new ItemSorter(){
    		@Override
    		public int score(VisualItem item) {
    			int score = sorter.score(item);
    			if (item.isInGroup(name+".bar")) {
					score ++;
				}
    			return score;
    		}
    	});
		drf.add(new InGroupPredicate(name+".bar"), new BagTreeMapRenderer());
		for (Iterator<Tuple> it = table.tuples(); it.hasNext();){
			Tuple tuple = it.next();
			EdgeItem bar = display.edge(name);
			display.focusTupleSet(name+".bar").addTuple(bar);
			setBarVisible(bar, false);
			tuple.set(VISUAL_BAR, bar);
			tuple.setDouble(BAG_LENGTH, bagLength(tuple));
			bar.setStroke(Pre.roundStroke(1));
			double size = bagSize(tuple);
			bar.getSourceItem().setSize(size);
			bar.getTargetItem().setSize(size);
			bar.setSize(bar.getSourceItem().getSize()*10);
			Pre.setBothCol(bar, moreColor);
			bar.getSourceItem().setShape(Constants.SHAPE_ELLIPSE);
			bar.getTargetItem().setVisible(false);
			bar.getSourceItem().setVisible(false);
		}
	}
	

	private Tuple highlightedBag = null;
	private double windowSize = -1;
	private double speed = 0;
	private double currentPosition = 0;
	private double sequenceTotalLength = 0;
	private static String POSITION = "position";
	private double bagGap = 10;
	public void setSpeed(double speed){
		this.speed = speed;
	}
	public double getSpeed(){
		return speed;
	}
	public void timmerBarPosition(long timeInMil){
		Map<Tuple, Double> map = getVisibleBars(timeInMil);
		double straightLength = Math.abs(center.getX()-otherEnd.getX());
		double curveLength = Math.PI*radius;
		double totalLength = curveLength+straightLength*2;
		double highLightFrac = 0;
		if (speed > 0) {
			highLightFrac = (currentPosition+windowSize/totalLength*(curveLength+straightLength))/sequenceTotalLength;
		}
		else{
			highLightFrac = (currentPosition+windowSize/totalLength*straightLength)/sequenceTotalLength;
		}
		if (highLightFrac < 0) {
			highLightFrac = 0;
		}
		if (highLightFrac > sequenceTotalLength){
			highLightFrac = 1;
		}
		if (curveDisplay != null){
			curveDisplay.wheelSetBar(highLightFrac);
		}
		boolean rightHandSide = center.getX()<otherEnd.getX();
		for(Tuple bag : map.keySet()){
			double frac = map.get(bag);
			EdgeItem bar = barItem(bag);
			double position = totalLength * frac;
			double temp = Math.abs(position-totalLength/2)-Math.PI*radius/2;
			if (temp >= 0) {
				if (rightHandSide) {
					Pre.setX(bar.getSourceItem(), center.getX()+temp);
				}
				else{
					Pre.setX(bar.getSourceItem(), center.getX()-temp);
				}
				int sign = !clockwise? -1 : 1;
				sign *= !rightHandSide? -1 : 1;
				sign *= frac <.5 ? -1 : 1;
				Pre.setY(bar.getSourceItem(), center.getY()+radius*sign);
				display.focusTupleSet(focusBagGroup).removeTuple(bag);
				straightOtherEnd(bag, bar);
			}
			else{
				double angle = (position - Math.abs(center.getX()-otherEnd.getX()))/radius;
				if (angle>Math.PI || angle <0 ) {
					throw new IllegalStateException("angle wrong = "+angle);
				}
				Point2D start = new Point2D.Double();
				Point2D end = new Point2D.Double();
				int sign = rightHandSide? 1 : -1;
				sign *= !clockwise? 1 : -1;
				start.setLocation(0, radius*sign);
				rotate(start, angle*sign, radius, 1, end);
				Pre.setPos(bar.getSourceItem(), end.getX()+center.getX(), end.getY()+center.getY());
				rotateOtherEnd(bag, bar);
				display.focusTupleSet(focusBagGroup).addTuple(bag);
			}
		}
	}
	public static void rotate(Point2D startPnt, double deltaAngle, double radius, double frac, Point2D result){
		double startAngle = Math.atan(startPnt.getY()/startPnt.getX());
		startAngle = startPnt.getX()<0? startAngle+Math.PI : startAngle;
		startAngle +=frac*deltaAngle;
		result.setLocation(radius*Math.cos(startAngle), radius*Math.sin(startAngle));
	}
	private void rotateOtherEnd(Tuple bag, EdgeItem bar){
		Point2D pos = Geom.toPntFracBetween(bar.getSourceItem(), bag.getDouble(BAG_LENGTH)/radius, center);
		Pre.setPos(bar.getTargetItem(), pos);
	}
	private void straightOtherEnd(Tuple bag, EdgeItem bar){
		Pre.setX(bar.getTargetItem(), bar.getSourceItem().getX());
		if (bar.getSourceItem().getY()<center.getY()) {//pointing down
			Pre.setY(bar.getTargetItem(), center.getY()-radius+bag.getDouble(BAG_LENGTH));
		}
		else{//pointing up
			Pre.setY(bar.getTargetItem(), center.getY()+radius-bag.getDouble(BAG_LENGTH));
		}
	}

	public String visualBagGroup = name+"_visual_bag_group";
	public String focusBagGroup = name+"_focus_bag_group";
	
	private void showBar(Tuple bag){
		double maxAverageCnt = 0;
		double curAverageCnt = 0;
		Iterator<Tuple> it =  display.focusTupleSet(visualBagGroup).tuples();
		while(it.hasNext()){
			Tuple tuple = it.next();
			double cnt = tuple.getInt("keyCnt");
			long[] ids = (long[]) tuple.get("group");
			if (ids != null && ids.length>0) {
				if(cnt/ids.length > maxAverageCnt) {
					maxAverageCnt = cnt / ids.length;
				}
				if (tuple.equals(bag)) {
					curAverageCnt = cnt/ids.length;
				}
			}
		}
		EdgeItem edge = barItem(bag);
		setBarVisible(edge, true);
		int color = ColorLib.interp(lessColor, moreColor, Math.sqrt(curAverageCnt/maxAverageCnt));
//		System.out.println("frac = "+Math.sqrt(curAverageCnt/maxAverageCnt));
//		System.out.println(color+","+curAverageCnt+",less"+lessColor+",more"+moreColor+",max"+maxAverageCnt);
		Pre.setBothCol(edge, color);
		Pre.setBothCol(edge.getSourceItem(), color);
	}
	private void hideBar(Tuple bag){
		EdgeItem edge = barItem(bag);
		setBarVisible(edge, false);
	}
	
	public static SimpleDateFormat displayFormat = new SimpleDateFormat("MMM d, yyyy");
	private void updateDisplayDate(){
		int tmpMin = DataLib.min(display.focusTupleSet(focusBagGroup), "date").getInt("date");
		if (tmpMin != minDate) {
			minDate = tmpMin;
			String displayDate;
			try {
				displayDate = displayFormat.format(integerDateFormat.parse(minDate+""));
				minDateItem.setString(WheelDisplay.TEXT, displayDate);
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}
		int tmpMax = DataLib.max(display.focusTupleSet(focusBagGroup), "date").getInt("date");
		if (tmpMax != maxDate) {
			maxDate = tmpMax;
			String displayDate;
			try {
				displayDate = displayFormat.format(integerDateFormat.parse(maxDate+""));
				maxDateItem.setString(WheelDisplay.TEXT, displayDate);
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}

	}
	private Map<Tuple, Double> getVisibleBars(long timeInMil){
		Map<Tuple, Double> result = new HashMap<Tuple, Double>();
		currentPosition = currentPosition + speed * timeInMil * 0.001;
		for(Iterator<Tuple> it = bagTable.tuples(); it.hasNext();){
			Tuple bag = it.next();
			double position = bag.getDouble(POSITION);
			if (position < currentPosition || position > currentPosition+windowSize) {
				display.focusTupleSet(visualBagGroup).removeTuple(bag);
			}
			else{
				display.focusTupleSet(visualBagGroup).addTuple(bag);
				barItem(bag).setVisible(true);
				result.put(bag, windowPosition(position-currentPosition));
			}
		}
		return result;
	}
	private void setBarVisible(EdgeItem bar, boolean visible){
//		bar.getSourceItem().setVisible(visible);
		bar.setVisible(visible);
	}
	private void initSlidingWindow(Table table){
		bagGap = 10;
		windowSize = bagGap * 75;
		speed = bagGap*4;
		bagTable.addColumn(POSITION, double.class);
		Tuple bag = firstBag();
		double pos = 0;
		while (bag != null){
			bag.setDouble(POSITION, pos);
			sequenceTotalLength = pos;
			pos += bagGap;
			bag = nextBag(bag);
		}
		currentPosition = 0-windowSize;
	}
		
	private double windowPosition(double relativePos){
		return relativePos/windowSize;
	}
	public void updateTotalKeywordCount(){
		for(Iterator<Tuple> it = bagTable.tuples(); it.hasNext();){
			Tuple bagTuple = it.next();
			int cnt = bagTuple.getInt("keyCnt");
			Tuple[] docTuples = getDocTuples(bagTuple);
			for (Tuple doc : docTuples) {
				for(int c = 0; c<doc.getColumnCount(); c++){
					if (doc.getColumnName(c).endsWith("_sent")) {
						int[] pos = (int[]) doc.get(c);
						if (pos != null) {
							cnt += pos.length;
						}	
					}
				}
			}
			bagTuple.setInt("keyCnt", cnt);
		}
		Iterator<Tuple> it =  display.focusTupleSet(visualBagGroup).tuples();
		while (it.hasNext()){
			showBar(it.next());
		}
	}
	public int minDate = 0;
	public int maxDate = 0;
	public VisualItem minDateItem = null;
	public VisualItem maxDateItem = null;
	private void setupDatePointer(){
		boolean rightHandSide = center.getX() < otherEnd.getX();
		int signForMinDateItem = rightHandSide ? -1 : 1;
		signForMinDateItem *= clockwise ? 1 : -1;
		minDateItem = display.mark.markText(WheelDisplay.TEXT, "min date",
				new Point2D.Double(center.getX(), center.getY()+(radius+40)*signForMinDateItem));
		maxDateItem = display.mark.markText(WheelDisplay.TEXT, "max date",
				new Point2D.Double(center.getX(), center.getY()-(radius+40)*signForMinDateItem));
		minDateItem.setSize(2);
		maxDateItem.setSize(2);
	}
    public void drawingBelt(){
    	Line2D beltAxis = new Line2D.Double(center, otherEnd);
    	for(int i = 0; i<10; i++){
    		int color = ColorLib.interp(outColor, innColor, (i+1)*0.1);
            EdgeItem coloredBelt = display.mark.markEdge("edges", beltAxis, color);
            Pre.setStkCol(coloredBelt, color);
            coloredBelt.setSize((radius-i*width/10)*2);
            coloredBelt.setStroke(Pre.roundStroke(1));
    	}
    	EdgeItem whiteBelt = display.mark.markEdge("edges", beltAxis, IntColor.gray(0));
    	whiteBelt.setStroke(Pre.roundStroke(1));
    	whiteBelt.setSize((radius-width)*2);
    }
    private void makeDoubleLinkList(Table table){
    	table.addColumn(PREVIOUS, int.class, 0);
    	table.addColumn(NEXT, int.class, 0);
    	for(int i = 0; i<table.getTupleCount(); i++){
    		table.setInt(i, PREVIOUS, i-1);
    		if (i == table.getTupleCount()-1) {
    			table.setInt(i, NEXT, Integer.MAX_VALUE);
    		}
    		else{
    			table.setInt(i, NEXT, i+1);
    		}
    	}
    }
	private void initBagTable(Table bagTable){
		makeDoubleLinkList(bagTable);
		initSlidingWindow(bagTable);
		makeVisualBar(bagTable);
		setupRelationField(bagTable);
		setupDatePointer();
		timmerBarPosition(5000);
	}
	public static String SIMILAR_IDX = "similar";
	public static String SIMILAR_VAL = "similar_value";
	private void setupRelationField(Table table){
		if (docData.getColumn(SIMILAR_IDX) == null) {
			docData.addColumn(SIMILAR_IDX, int[].class);
			docData.addColumn(SIMILAR_VAL, double[].class);
			Map<Integer, List<Pair<Integer>>> map = CurveGen.collectTopMutualDocs(companyName, 10);
			for(Iterator<Tuple> it = docData.tuples(); it.hasNext(); ){
				Tuple docTuple = it.next();
				List<Pair<Integer>> pairList = map.get((int)docTuple.getLong(Article.FLD_ID));
				int[] ids = new int[pairList.size()];
				double[] vals = new double[pairList.size()];
				for(int i = 0; i<pairList.size(); i++){
					ids[i] = pairList.get(i).key;
					vals[i]= pairList.get(i).value.doubleValue();
				}
				docTuple.set(SIMILAR_IDX, ids);
				docTuple.set(SIMILAR_VAL, vals);
			}
			String file = Common.wheelFolder(companyName)+"docInfo_.xml";
			Graph g = new Graph(docData, false);
			try {
				new ArraySupportedGraphWriter().writeGraph(g, new File(file));
			} catch (DataIOException e) {
				e.printStackTrace();
			}
		}
		table.addColumn(SIMILAR_IDX, int[].class);
		table.addColumn(SIMILAR_VAL, double[].class);
		Map<Long, Integer> map = new HashMap<Long, Integer>();
		for(Iterator<Tuple> it = table.tuples(); it.hasNext(); ){
			Tuple bag = it.next();
			Tuple[] docs = getDocTuples(bag);
			for (Tuple tuple : docs) {
				map.put(tuple.getLong(Article.FLD_ID), bag.getRow());
			}
		}
		for(Iterator<Tuple> it = table.tuples(); it.hasNext(); ){
			Tuple bag = it.next();
			Tuple[] docs = getDocTuples(bag);
			List<Pair<Integer>> list = new ArrayList<Pair<Integer>>();
			for (Tuple tuple : docs) {
				int[] ids = (int[]) tuple.get(SIMILAR_IDX);
				double[] vals = (double[]) tuple.get(SIMILAR_VAL);
				for(int i =0; i<ids.length; i++){
					list.add(new Pair<Integer>(map.get((long)ids[i]), vals[i]));
				}
			}
			int[] idxs = new int[5];
			double[] vals = new double[5];
			Set<Integer> set = new HashSet<Integer>();
			Collections.sort(list, Pair.DESCENDING);
			for (Pair<Integer> pair : list) {
				if (!set.contains(pair.key)) {
					idxs[set.size()] = pair.key;
					vals[set.size()] = pair.value.doubleValue()*3;
//					System.out.println(pair.value.doubleValue()*3);
					set.add(pair.key);
					if (set.size() == 5) {
						break;
					}
				}
			}
//			for(int i = 0; i<idxs.length; i++){
//				idxs[i] = Pre.randColor.nextInt(table.getTupleCount());
//				vals[i] = Pre.randColor.nextDouble()*3;
//			}
			bag.set(SIMILAR_IDX, idxs);
			bag.set(SIMILAR_VAL, vals);
		}
	}
	public Tuple nextBag(Tuple bagContent){
		int row = bagContent.getInt(NEXT);
		if (row == Integer.MAX_VALUE) {
			return null;
		}
		return bagTable.getTuple(row);
	}
	public Tuple prevBag(Tuple bagContent){
		int row = bagContent.getInt(PREVIOUS);
		if (row == -1) {
			return null;
		}
		return bagTable.getTuple(row);
	}
	public Tuple firstBag(){
		return DataLib.min(bagTable, PREVIOUS);
	}
	public Tuple lastBag(){
		return DataLib.max(bagTable, NEXT);
	}
	public static String PREVIOUS = "previous";
	public static String NEXT = "next";
}